home *** CD-ROM | disk | FTP | other *** search
- /*
- Copyright (c) 1986, 1990 by The Trustees of Columbia University in
- the City of New York. Permission is granted to any individual or
- institution to use, copy, or redistribute this software so long as it
- is not sold for profit, provided this copyright notice is retained.
-
- Author: Andrew Lowry
- */
- /* cmkey
- **
- ** Code to parse keywords. Parsing succeeds if current input contains
- ** a delimiter, and the input up to that delimiter is nonempty and
- ** uniquely matches a non-ignored keyword in the supplied keyword
- ** table. Completion succeeds in the same circumstances, except that
- ** no delimiter will be present, and returns the remainder of the
- ** matched keyword. Standard help prints a list of all visible keywords
- ** that might match the current input. The break table specifies which
- ** characters are allowed in keywords. The standard table allows letters,
- ** digits, and hyphens in all positions.
- **/
-
- #define KEYERR /* keyword error tbl allocated here */
-
- #include "ccmdlib.h" /* get standard symbols */
- #include "cmfncs.h" /* and internal symbols */
-
- /* Forward declaration of handler routines */
-
- int keyprs(), keyhlp(), keycplt();
-
- static brktab keybrk = { /* standard break table */
- { /* 1st char break array */
- /* all but letters, digits, hyphen */
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x00, 0x3f,
- 0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x1f
- },
- { /* subsequent char break array */
- /* same as above */
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x00, 0x3f,
- 0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x1f
- }
- };
-
- ftspec ft_key = { keyprs, keyhlp, keycplt, 0, &keybrk }; /* handler structure */
-
-
-
- /* keyprs - Succeed if the current input is terminated and matches
- ** a single keyword (or exactly matches a non-ignored keyword).
- **/
-
- PASSEDSTATIC int
- keyprs(text,textlen,fdbp,parselen,value)
- char *text;
- int textlen,*parselen;
- fdb *fdbp;
- pval *value;
- {
- int mcount; /* number of matches */
- keywrd *mat; /* the matching keyword */
- char *term; /* char terminating keyword */
- keytab *kt; /* the keyword table */
-
- mcount = match(text,textlen,fdbp,&mat,&term); /* find matching keys */
- if (mcount == 0)
- return(KEYxNM); /* no match */
- else if (term == NULL) /* not terminated? */
- return(CMxINC); /* then incomplete parse */
- else if (mcount > 1)
- return(KEYxAMB); /* ambiguous */
-
- kt = (keytab *) fdbp->_cmdat; /* unique - get table address */
- while ((mat)->_kwflg & KEY_ABR) /* track down abbreviations */
- if (((mat)->_kwval >= kt->_ktcnt) || /* abbrev ptr out of bounds? */
- ((mat)->_kwval < 0)
- )
- return(KEYxABR); /* bad abbrev chain */
- else
- mat = &(kt->_ktwds[mat->_kwval]); /* move down chain */
-
- *parselen = term - text; /* compute field length */
-
- if (fdbp->_cmffl & KEY_EMO) { /* exact match only? */
- if (*parselen != strlen (mat->_kwkwd))
- return KEYxNM; /* it's not a match */
- }
- if (fdbp->_cmffl & KEY_PTR)
- value->_pvkey = (keyval) mat; /* copy value from keyword */
- else
- value->_pvkey = mat->_kwval;
- return(CMxOK); /* and give a good parse */
- }
-
-
-
- /* keycplt - If the current input is ambiguous, we beep. Otherwise
- ** we complete with the remainder of the identified keyword. In the
- ** latter case, full completion adds a space and a wakeup, while
- ** partial completion stops after punctuation.
- **/
-
- PASSEDSTATIC int
- keycplt(text,textlen,fdbp,full,cplt,cpltlen)
- char *text,**cplt;
- int textlen,full,*cpltlen;
- fdb *fdbp;
- {
- int mcount; /* number of matching keywords */
- keywrd *mat; /* unique matching key */
- char *term; /* char terminating the keyword */
- keytab *kt; /* the keyword table */
-
- *cplt = NULL; /* assume empty completion */
- mcount = match(text,textlen,fdbp,&mat,&term); /* find matching keys */
- if (mcount > 1) {
- char *partial();
- *cplt = partial(text,textlen, fdbp->_cmdat,mcount);
- *cpltlen = strlen(*cplt);
- return(CMP_BEL); /* beep if ambiguous */
- }
- else {
- kt = (keytab *) fdbp->_cmdat; /* unique - get table address */
- while ((mat)->_kwflg & KEY_ABR) /* track down abbreviations */
- mat = &(kt->_ktwds[mat->_kwval]); /* move down chain */
-
- *cplt = mat->_kwkwd+textlen; /* point at completion text */
- *cpltlen = strlen(mat->_kwkwd) - textlen; /* compute completion len */
- if (*cpltlen < 0) /* can happen on weird abbrevs */
- *cplt = NULL; /* no completion in that case */
- if (full) /* full completion? */
- return(CMP_SPC | CMP_GO); /* succeed with space and wakeup */
- else
- return(CMP_PNC); /* partial stops after punctuation */
- }
- }
-
- /* keyhlp - Construct a set of matching keywords for the current input,
- ** then print them in tabular form. Do not print invisible keywords.
- ** If no keywords match, print a special indication. The help message
- ** starts with "keyword, one of the following:". If custom help has
- ** been given, "keyword, " is left off of this string.
- **/
-
- PASSEDSTATIC int
- keyhlp(text,textlen,fdbp,cust,lines)
- char *text;
- int textlen,cust;
- fdb *fdbp;
- int lines;
- {
- int mcount; /* number of matches */
- keywrd *mat; /* a matching keyword */
- char *term; /* character terminating input */
- keytab *kt; /* keyword table */
- int i,j;
- int keylen; /* lengths of individual keywords */
- int cols; /* number of words printed per line */
- int curcol; /* current table column */
- int maxlen = 0; /* maximum keyword length */
- int mylines = 0;
-
- if (!cust)
- cmxputs("keyword, "); /* start of msg with no custom help */
- cmxputs("one of the following:"); /* remainder of the first line */
-
- mcount = match(text,textlen,fdbp,&mat,&term); /* find matching keys */
- if (mcount == 0) { /* no match */
- cmxputs("\n");
- lines--;
- if (lines == 0) {
- if (!cmhelp_more("--space to continue, Q to stop--"))
- return(-1);
- else {
- lines = cmcsb._cmrmx;
- mylines = 0;
- }
- }
- cmxputs(" (No keywords match current input)"); /* indicate void */
- return(lines-1);
- }
- mylines = 1;
- kt = (keytab *) fdbp->_cmdat; /* get keywrd table from FDB */
- mat = kt->_ktwds; /* point to first keyword */
- if ((fdbp->_cmffl & KEY_WID) && /* fixed width requested */
- (kt->_ktwid > 0)) { /* sanity check */
- maxlen = kt->_ktwid;
- }
- else {
- for (i = 0; i < kt->_ktcnt; i++) { /* first pass to find longest kwd */
- if ((mat->_kwflg & KEY_MAT) && /* matching keyword... */
- ((mat->_kwflg & KEY_INV) == 0) /* and not invisible */
- ) {
- keylen = strlen(mat->_kwkwd); /* get keyword length */
- if (keylen > maxlen)
- maxlen = keylen; /* update longest size */
- }
- mat++; /* and move to next keyword */
- }
- maxlen += 3; /* adjust for column separation */
- }
-
- /* Following calculation goes as follows:
- ** Let w be the screen width. Naively, we would calculate that
- ** we could list floor(w/maxlen) keywords per line, each taking up
- ** maxlen columns on the screen. But since each line is indented
- ** two spaces, we should subtract 2 from the effective screen width.
- ** Then we add 3 to the effective width because we will not need to
- ** print three spaces after the last keyword in a line. So the
- ** effective screen width is w-2+3 = w+1 = cmcsb._cmcmx+2, since
- ** cmcsb._cmcmx is one less than the screen width. So we want
- ** floor((cmcsb._cmcmx+2)/maxlen) keywords per line.
- **/
-
- /*
- * The reasoning above ignores the problem with automargins on non-"xn"
- * terminals. On an 80-column terminal with three columns of 24-char
- * keywords, for instance, the terminal will wrap around to the next
- * line and the cmxnl() below would be inappropriate. So until someone
- * cleans up the rest of the ccmd output routines, we have to avoid the
- * rightmost column.
- *
- * MM happens to have 24-character keywords which would look kind of silly
- * displayed in only two columns, so we compensate by subtracting one of
- * the spaces displayed at the beginning of the line. TOPS-20 only prints
- * one space at the beginning of the line, so I don't feel too guilty
- * about this... -- chris
- */
-
- cols = (cmcsb._cmcmx+2) / maxlen; /* number of columns per line */
- if (cols < 1) cols = 1; /* don't divide by zero */
-
- mat = kt->_ktwds; /* point to first keyword */
- curcol = 0; /* currently printing first column */
- for (i = 0; i < kt->_ktcnt; i++) { /* second pass to print matches */
- if ((mat->_kwflg & KEY_MAT) && /* matching keyword? */
- ((mat->_kwflg & KEY_INV) == 0) /* and visible? */
- ) {
- if (curcol == 0) {
- cmxnl(); /* new line for first column */
- if (mylines >= lines) {
- if (!cmhelp_more("--space to continue, Q to stop--"))
- return(-1);
- else {
- lines = cmcsb._cmrmx;
- mylines = 0;
- }
- }
- mylines++; /* and count the line */
- cmxputs(" "); /* and offset a bit */
- }
- cmxputs(mat->_kwkwd); /* print the keyword */
- if (curcol < (cols-1)) /* space out if not last column */
- for (j = strlen(mat->_kwkwd); j < maxlen; j++)
- cmxputc(SPACE);
- curcol = (curcol+1) % cols; /* and move to next column */
- }
- mat++; /* move to next keyword */
- }
- return(lines - mylines); /* all done */
- }
-
-
-
- /* match - Auxiliary routine used by keyword handlers.
- **
- ** Purpose:
- ** Step through all the keywords in a table, and set their KEY_MAT
- ** flags according to whether or not they match a given input string.
- ** Returns the number of matching keywords, and if only one keyword
- ** matches, a pointer to that keyword. If more than one keyword
- ** matches, but one keyword matches exactly, it is returned as if
- ** it were the only matching keyword. KEY_INV, KEY_NOR and KEY_ABR
- ** flags do not affect this operation, except that an exact match
- ** to a KEY_NOR flagged keyword will not be considered exact.
- **
- ** Input arguments:
- ** text - A pointer to the first input character.
- ** textlen - The number of input characters.
- ** fdbp - A pointer to the FDB pointing to the keyword table
- ** and break table to be used.
- **
- ** Output arguments:
- ** mat - A pointer to the matching keyword, if exactly one keyword
- ** matched or if one of the matching keywords was an exact match.
- ** term - A pointer to the first character following the input that
- ** was used to match keywords, if any characters were left after
- ** that input. Otherwise, NULL is returned here.
- **
- ** Returns: The number of matching keywords (or 1 for an exact match).
- **/
-
- static int
- match(text,textlen,fdbp,mat,term)
- char *text,**term;
- int textlen;
- fdb *fdbp;
- keywrd **mat;
- {
- int mcount = 0; /* number of matches seen */
- int inlen; /* # of chars to match in input */
- int i;
- int exact = FALSE; /* true if exact match occurs */
- keywrd *kwds; /* for stepping through table */
- brktab *btab; /* break table to use */
- keytab *kt; /* keyword table to search */
-
- if ((btab = fdbp->_cmbrk) == NULL) /* get supplied break table */
- btab = &keybrk; /* or use default */
-
- for (inlen = 0; inlen < textlen; inlen++) /* find # of usable chars */
- if (BREAK(btab,text[inlen],inlen)) /* stop on first break char */
- break;
- if (inlen == textlen) /* no break char? */
- *term = NULL; /* then set no terminator */
- else
- *term = text+inlen; /* else point to it for caller */
-
- kt = (keytab *) fdbp->_cmdat; /* point to keyword table */
- if (inlen == 0 && textlen != 0)
- return(0);
- kwds = kt->_ktwds; /* point to first keyword */
- for (i = 0; i < kt->_ktcnt; i++) { /* step through table */
- if (match1(kwds->_kwkwd,text,inlen)) {
- kwds->_kwflg |= KEY_MAT; /* this keyword matches */
- if (!exact) { /* if no prior exact match */
- *mat = kwds; /* then save pointer to return */
- mcount++; /* and count it */
- }
- if (strlen(kwds->_kwkwd) == inlen) /* exact match? */
- if ((kwds->_kwflg & KEY_NOR) == 0) { /* and not ignored? */
- exact = TRUE; /* flag this exact match */
- mcount = 1; /* only one match now */
- }
- }
- else
- kwds->_kwflg &= ~KEY_MAT; /* no match -- turn off flag */
-
- kwds++; /* move on to next keyword */
- }
- kwds = kt->_ktwds;
- if (mcount == 1)
- for (i = 0; i < kt->_ktcnt; i++,kwds++) { /* step through table */
- if (kwds->_kwflg & KEY_MAT && kwds->_kwflg & KEY_NOR) {
- kwds->_kwflg &= ~KEY_MAT;
- mcount = 0;
- *mat = NULL;
- break;
- }
- }
- return(mcount); /* give back # of matches */
- }
-
-
-
- /* match1 - Auxiliary routine for match
- **
- ** Purpose:
- ** Decides whether or not two strings match up to a given number of
- ** characters. Case of letters is ignored in the comparison.
- **
- ** Input arguments:
- ** s1, s2 - Pointers to the strings to be compared.
- ** slen - Number of characters to compare.
- **
- ** Output arguments: None.
- ** Returns: TRUE for a match, FALSE otherwise.
- **/
-
- static int
- match1(s1,s2,slen)
- char *s1,*s2;
- int slen;
- {
- char c1,c2; /* individual chars to compare */
- while (slen-- > 0) { /* step through strings */
- c1 = (*s1++) & CC_CHR; /* pick up next pair of chars */
- c2 = (*s2++) & CC_CHR;
- if (islower(c1))
- c1 = toupper(c1); /* upper case first char */
- if (islower(c2))
- c2 = toupper(c2); /* upper case second char */
- if (c1 != c2)
- return(FALSE); /* mismatch */
- }
- return(TRUE); /* all chars matched */
- }
-
- PASSEDSTATIC char *
- partial(text,textlen,kt,pcount)
- char *text;
- int textlen;
- keytab *kt;
- int pcount;
- {
- int i;
- static char buf[50];
- keywrd *k;
- int first = TRUE;
- int len;
-
- buf[0] = '\0';
- for(i = 0; i < kt->_ktcnt; i++) {
- k = &kt->_ktwds[i];
- if (!(k->_kwflg & KEY_MAT))
- continue;
- if (k->_kwflg & KEY_NOR)
- continue;
- if (first) {
- strcpy(buf,k->_kwkwd);
- first = FALSE;
- }
- else {
- len = matchlen(buf,k->_kwkwd);
- buf[len] = '\0';
- if (len == textlen)
- break;
- }
- }
- return(&buf[textlen]);
- }
-
- #ifdef toupper
- #undef toupper
- #endif
- #define toupper(c) (islower(c) ? (c)-'a'+'A' : (c))
- matchlen(s1,s2)
- register char *s1, *s2;
- {
- register int i;
-
- for(i = 0; (toupper(*s1) == toupper(*s2)); s1++,s2++,i++);
- return(i);
- }
-
-